package influence.propagation.implementation;

import influence.propagation.core.Model;
import influence.propagation.core.NodeModel;
import influence.propagation.core.OutputMode;
import influence.propagation.core.ResultSet;
import influence.propagation.core.ResultSetBuilder;
import java.util.LinkedList;
import java.util.Queue;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.traversal.Evaluator;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.graphdb.traversal.Traverser;
import org.neo4j.graphdb.traversal.Uniqueness;

/**
 * Poiché l'algoritmo scelto non dipende dal tipo del risultato, L'AbstractModel fornisce un'implementazione 
 * iniziale dell'aggiunta al ResultSet
 * @author Valerio
 *
 */
public abstract class AbstractModel implements Model{
	
	protected GraphDatabaseService db;
	private Queue<NodeModel> activeNode = new LinkedList<NodeModel>();
	private ResultSetBuilder builder = null;
	
	protected AbstractModel(GraphDatabaseService db){
		this.db=db;
	}
	
	/**
	 * Restituisce un nodemodel in base alla struttura dell'algoritmo
	 * @param node
	 * @return
	 */
	protected abstract NodeModel createNewActiveNode(Node node);
	
	/**
	 * Restituisce il valutatore dell'algorimo
	 * @return
	 */
	protected abstract Evaluator getEvaluator();
	
	@Override
	public ResultSet executeAlgoritm(OutputMode mode) {
		if (mode.equals(OutputMode.BREADTH)) {
			builder = new BreadthResultSetBuilder();
		} else {
			builder = new DepthResultSetBuilder();
		}
		Transaction transaction = null;// alias
		TraversalDescription td = getTraverlsalDescription();
		try (Transaction tx = db.beginTx()) {
			transaction = tx;
			Node dbNode = null;
			while (!activeNode.isEmpty()) {
				NodeModel node = activeNode.poll();
				builder.putInitialActiveNode(node);
				dbNode = findNode(node);
				Traverser traverse = td.traverse(dbNode);
				int iteration = 0;
				for (Path path : traverse) {
					Node newActiveNode = path.endNode();
					System.out.println(newActiveNode.getProperty("NAME"));
					boolean isNewTraverse = iteration != path.length();
					iteration = path.length();
					NodeModel myNode = createNewActiveNode(newActiveNode);
					putToResult(myNode, isNewTraverse);
				}
			}
			transaction.success();
		}
		return builder.getIstance();
	}

	private void putToResult(NodeModel myNode,
			boolean isNewTraverse) {
		builder.put(myNode, isNewTraverse);		
	}

	private Node findNode(NodeModel node) {
		return db.getNodeById((long) node
				.getProperty(ActiveNodeProperties.ID));
	}

	private TraversalDescription getTraverlsalDescription() {
		Evaluator ev = getEvaluator();
		return db.traversalDescription().breadthFirst()
				.relationships(RelType.WEIGHT_EDGE, Direction.OUTGOING)
				.evaluator(ev).uniqueness(Uniqueness.RELATIONSHIP_GLOBAL);
	}

	/**
	 * Aggiunge un nodo attivo al modello
	 */
	protected void addActiveNode(NodeModel node){
		activeNode.add(node);
	}
}
